Hacks布局篇-Hack2 使用include标签避免代码重复

<include />

作者:李旺成
时间:2016年5月6日


在这个 Hack 中将详细介绍 标签的使用,以及一些注意事项。

一、每个页面添加页脚

添加页脚示意图

假设:现在有这样一个需求,为应用中的每个页面都添加一个页脚。这里简单处理,要添加的页脚就是一个显示应用名称的文本(使用 TextView 来显示),如上图所示。大多数的应用都是由多个 Activity 组成的,多个 Activity 一般对应多个布局文件。那是不是要把这个页脚 TextView 一个个拷贝到每个布局文件中?

听过这么一句话:当你的程序中出现大量的重复代码时你得小心了。(不是我杜撰的,大致是这个意思,没有找到出处…)

确实是这样,不要把一段相同的代码到处“复制/粘贴”,有一个很明显的弊端摆在眼前,如果以后需要修改这段代码就悲剧了。

所以,要实现上述需求“复制/粘贴”不是我们要的解决方案。解决该问题最简单的方法是使用 标签。当然,使用 style 来保证这些页脚都使用同一个样式也是可以的,但没有 标签简便,再说 style 的主要功能可不是用来解决重复代码问题的。

好了需求明确的,看看怎么实现。

使用 标签为页面添加页脚

首先,创建一个页脚布局 footer_app_name.xml:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:gravity="center"
android:text="AndroidHacksLayout"
android:textColor="@android:color/holo_red_dark"/>

然后,使用 为布局添加页脚 activity_hack2_1.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.diygreen.androidhackslayout.Hack2Activity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="演示使用 <include /> 标签避免代码重复"/>
<!-- 插入页脚 -->
<include
layout="@layout/footer_app_name"/>
</RelativeLayout>

上面的示例代码很简单,为 标签的 layout 属性指定一个表示页脚的布局即可。仔细一点的同学可能看到了:页脚布局中设置了 android:layout_alignParentBottom=”true” 属性,那么问题来了,这个属性是 RelativeLayout 中子控件才有用,如果把 activity_hack2_1.xml 的根布局修改为 FrameLayout 那不就不行了吗?

根布局修改为 FrameLayout

上图中可以看到,将根布局修改为 FrameLayout 之后,“页脚”就不是在该页面的底部了,那要怎么做了?不急,下面会给出解决方案,且继续往下阅读。

二、 标签使用详解

标签使用虽然挺简单的,但也有一些细节要注意,这里做个简单的总结供大家参考。

include 属性有两种设置方式

1、在子布局中设置
就是上面的示例代码中使用的方式,直接在子布局中设置好所有的属性,使用的时候只需要在主布局中为 include 设置 layout 属性即可。

这样用很方便,但是 Android 中提供了很多布局技术,你在子布局中设置的属性可能只适用与某一个/某一类布局。上面的示例中就是这样,只有主布局为 RelativeLayout 的时候页面才能在指定位置显示,如果替换为 FrameLayout 就显示不正确了。这就反映了一个问题 —— 不灵活。那怎么解决呢?include 属性还有另外一种设置方式。

2、在 标签里设置
说到这里,那先把示例中遗留的问题解决了,就是将 RelativeLayout 替换为 FrameLayout 之后页面位置的问题。

先看解决后的效果:

在 <include /> 标签里设置属性

解决方案代码:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
>
...
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
layout="@layout/footer_app_name"/>
</FrameLayout>

上面的示例代码中与根布局为 RelativeLayout 对比的主要修改处是在 标签中添加了 android:layout_gravity 属性,并将其属性值设置为了 “bottom” ,这就可以保证页脚会在页面的底部显示了。

说明:其实和 style 的使用非常类似,你可以直接使用 style,也可以覆盖 style 中的一些属性。

标签里设置属性一般是这样用的,下面使用添加一个页眉作为示例:

  1. 创建页面 header_app_name.xml:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:gravity="center"
    android:text="我是-页眉-"
    android:textColor="@android:color/holo_blue_dark"/>
    ```

    2. 使用 <include /> 标签添加页眉
    ```java
    <include
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="20dp"
    layout="@layout/header_app_name"/>

效果如下:

添加页眉

注意:在示例中页面布局的宽度高度都设置为 0dp 了,这么做的目的就是由 header_app_name.xml 文件的使用者在 标签中指定 layout_width 和 layout_height 属性。如果使用者不指定这两个属性,它们的默认值都是 0dp,那便看不到页眉。

三、 标签注意事项

这里罗列了一些我在使用 标签的时候踩过的一些坑,希望大家以后可以绕过去。

findViewById 出现 NullPointerException

具体情况是这样的,如果 include 一个布局时,并没有给 标签设置 id 属性,那么你直接使用 findViewById 来找 include 指定布局中控件是没有问题的。

但是,一旦你为 标签设置了 id ,就不能直接把它里面的控件当成主布局文件中的控件来直接获取了,必须先获得这个 标签指定的布局文件,再通过该布局文件 findViewById 来获得其子控件。

标签失效了

Android 的缺陷(Issue)跟踪系统中报告过一个缺陷,缺陷的标题是:“ 标签失效了,如果想通过 标签的属性覆盖被包含的布局所指定的属性是行不通的。”。

这个 issue 描述的问题在一定程度上是正确的,问题出在如果想在 标签中覆盖被包含布局所指定的任何 android:layout_* 属性,必须在 标签中同时指定 android:layout_width 和 android:layout_height 这两个属性。(参考自:《50 Android Hacks》)

PS:话说如果你是用 AndroidStudio 的话,可能根本就不会遇到这个问题,因为,如果你在指定其他的 android:layout_* 属性时,如果没有同时指定 android:layout_width 和 android:layout_height 这两个属性,AndroidStudio 是会报错的:

提示添加 layout_width 和 layout_height

自定义控件与

如果是一些比较简单,include 进来之后不需要有过多操作的,使用 完全可以胜任了。对于一些比较复杂,而且要添加很多响应事件等的使用场景,建议使用自定义控件。如果还是使用 标签,那么不可避免在 Java 代码中又得写一堆类似的代码来添加相应逻辑。

项目地址

AndroidHacks合集
布局篇
个人博客
示例用到代码见:
Hack2Activity.java
activity_hack2_1.xml
activity_hack2_2.xml
header_app_name.xml
footer_app_name.xml

坚持原创技术分享,您的支持将鼓励我继续创作!